#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <malloc.h>

#include "lib.h"
#include "stream.h"
#include "rresource.h"
#include "rentrywriter.h"
#include "rfilewriter.h"
#include "filesystem.h"
using namespace Library;

#include "zlib.h"

#define SUPER	ResourceEntry
#define SELF	ResourceEntryWriter


/**
 * Creates an empty, non-persistent resource entry useful for loading
 * from disk a resource header via load() and its resource data loadData().
 */
ResourceEntryWriter::ResourceEntryWriter()
	: SUPER()
{
	m_persistent		= false;
	m_osizecompressed	= 0;
	m_ostream			= 0;
	m_ooffset			= 0;
}

/**
 * Creates a persistent (in memory) resource entry with associated 
 * resource data.
 */
ResourceEntryWriter::ResourceEntryWriter(const String & name, uint32 size,
	void * data)
	: SUPER()
{
	m_name				= name;
	m_size				= size;
	m_timestamp			= time(0);
	m_data				= Resource::allocate(this, size);

	m_persistent		= true;
	m_osizecompressed	= 0;
	m_ostream			= 0;
	m_ooffset			= 0;

	if (m_data)
		memcpy(m_data, data, size);
}

/**
 *
 */
ResourceEntryWriter::~ResourceEntryWriter()
{
}

/**
 * @param is	Optional pointer to stream from which the resource should be
 *				loaded if it is not already in memory.
 * @return		A pointer to the resource data on success, 0 on failure.
 *
 * Retrieves a pointer to the resource data and increments the use count.  If
 * the resource is not currently in memory, non-persistent, and stream @e is
 * is non-null, the resource will be loaded from the stream.
 */
void *
ResourceEntryWriter::acquire()
{
	if (m_persistent)
	{
		if (m_data)
			m_usecount++;

		return m_data;
	}

	return SUPER::acquire();
}


bool8
ResourceEntryWriter::name(const String & name)
{
	if (!name.getLength())
		return false;

	m_name		= name;
	m_timestamp	= time(0);

	return true;
}


/**
 * @return		@b true on success, @b false on failure.
 *
 * Decrements the use count.  If the use count falls to zero (0) and the
 * resource data is not persistent, the resource data is freed from memory.
 */
bool8
ResourceEntryWriter::release()
{
	if (m_persistent)
	{
		if (m_usecount)
		{
			m_usecount--;
			return true;
		}

		return false;
	}

	return SUPER::release();
}


/**
 * @param os	Pointer to a stream to which the resource header should be
 *				saved.
 * @param st	Pointer to a string table to which the resource name is stored.
 * @return		@b true on success, @b false on failure.
 *
 * Saves the resource header to stream @e os at the current stream's file
 * offset.  The stream's file offset is advanced by the number of bytes written
 * to the stream; equal to the size of the resource header upon success and
 * something less than the size of the resource header, maybe even 0, upon
 * failure.  This means it is the calling class' responsibility to save and
 * restore the stream's offset in the event of failure.
 *
 * <b>Developer Notes:</b>
 *
 * When called, this method saves attribute @e offset which is set by the
 * method saveData().  Therefore, it is important to call saveData() before
 * invoking this method.
 */
bool8
ResourceEntryWriter::save(uint8 format)
{
	// No matter the format, we must have a valid output stream pointer.
	if (!m_ostream)
		return false;

	switch (format)
	{
		// ResourceEntry::DiskImage_versionD
		case RE_DISKIMAGEFORMAT_VERSION_D:
		{
			DiskImage_versionD	di;
			char *				name;

			// In an attempt to minimize the chances of a stack overflow in
			// the client, or whomever is reading resource files, we
			// artifically restrict the resource name length to maximum path.
			if (m_name.getLength() >= FileSystem::maxPath())
				return false;

			di.m_offset			= m_ooffset;
			di.m_size			= m_size;
			di.m_sizecompressed = m_osizecompressed;
			di.m_timestamp		= m_timestamp;
			di.m_namelength		= m_name.getLength();

			if (m_ostream->write(&di, sizeof di) != sizeof di)
				return false;

			// If _alloca fails it will generate a structured stack overflow
			// exception, so the 'if' statement below is worthless ... but it
			// makes me feel better.  -kral  07.12.2004
			name = (char *)_alloca(di.m_namelength + 1);
			if (!name)
				return false;

			StringConversions::convert(name, m_name, di.m_namelength + 1);

			if (m_ostream->write(name, di.m_namelength) != (int32)di.m_namelength)
				return false;

			return true;
			break;
		}

		// ResourceEntry::DiskImage_versionC
		case RE_DISKIMAGEFORMAT_VERSION_C:
		{
			DiskImage_versionC	di;

			// Truncating resource names would be pointless as this would
			// break the client; wasting development's time when they have
			// to debug the code.  Therefore, it's an error.
			if (m_name.getLength() >= sizeof di.m_name)
				return false;

			StringConversions::convert(di.m_name, m_name, sizeof(di.m_name));
			di.m_offset			= m_ooffset;
			di.m_size			= m_size;
			di.m_sizecompressed = m_osizecompressed;

			if (m_ostream->write(&di, sizeof di) != sizeof di)
				return false;

			return true;
		}

		// ResourceEntry::DiskImage_versionA
		case RE_DISKIMAGEFORMAT_VERSION_A:
		{
			DiskImage_versionA	di;

			// Truncating resource names would be pointless as this would
			// break the client; wasting development's time when they have
			// to debug the code.  Therefore, it's an error.
			if (m_name.getLength() >= sizeof di.m_name)
				return false;

			// This format does not support compressed data.
			if (m_osizecompressed)
				return false;

			StringConversions::convert(di.m_name, m_name, sizeof(di.m_name));
			di.m_offset	= m_ooffset;
			di.m_size	= m_size;

			if (m_ostream->write(&di, sizeof di) != sizeof di)
				return false;

			return true;
		}

		// Unknown disk image format.
		default:
		{
			return false;
			break;
		}
	}
}


/**
 * @param os	Pointer to a stream to which the resource data should be saved.
 * @return		@b true on success, @b false on failure.
 *
 * Saves the resource data to stream @e os at the current stream's file offset.
 * The stream's file offset is advanced by the number of bytes written to the
 * stream; equal to the size of the resource data upon success and something
 * less than the size of the resource data, maybe even 0, upon failure.  This
 * means it is the calling class' responsibility to save and restore the
 * stream's offset in the event of failure.
 *
 * <b>Developer Notes:</b>
 *
 * When called, this method queries the stream for its current file offset
 * and stores it in attribute @e offset.  @e offset is stored to the the stream
 * when save() is called.
 */
bool8
ResourceEntryWriter::saveData(pStream os, bool8 compression)
{
	void *	resource;
	uint8 *	zdata;
	uint32	zsize;
	bool8	result = false;

	// Make sure we have a valid stream.
	if (!os)
		return false;

	// Store off some information for later use.
	m_ostream = os;
	m_ooffset = os->tell();

	// Make sure the information is valid.
	if (m_ooffset == Stream::eof)
		return false;

	// Ensure the data is in-memory by acquiring it (increments use-count).
	resource = acquire();
	if (resource && m_data && m_size)
	{
		if (compression)
		{
			// Allocate a temporary buffer for data compression.  According to
			// zlib we need m_size + 0.01% + 12 bytes minimum, but instead
			// let's just double the size (with a 1024 byte minimum).
			zsize = MAX(m_size << 1, 1024);
			zdata = (uint8 *)malloc(zsize);
			if (zdata)
			{
				// Compress the data.
				if (compress(zdata, &zsize, (uint8 *)m_data, m_size) == Z_OK)
				{
					// Albeit slim, there is a chance the compressed data is
					// larger than its uncompressed self - ensure we only write
					// out the smaller of the two.
					if (zsize < m_size)
					{
						// Compressed data.
						if (m_ostream->write(zdata, zsize) == (int32)zsize)
						{
							m_osizecompressed = zsize;
							result = true;
						}
					}
					else
					{
						// Uncompressed data.
						if (m_ostream->write(m_data, m_size) == (int32)m_size)
							result = true;
					}
				}
				
				// No need to save - it's on the disk now.
				free(zdata);
			}
		}
		else
		{
			if (m_ostream->write(m_data, m_size) == (int32)m_size)
				result = true;
		}

		// Now release what we have acquired (decrements use-count).
		Resource::release(resource);
	}

	return result;
}


String
ResourceEntryWriter::tocHeader()
{
	String	header;

	header.sprintf("\n  %-10s  %-8s  %-8s  %-8s  %-8s  %s"
				   "\n  %-10s  %-8s  %-8s  %-8s  %-8s  %s"
				   "\n%s",
				   "",     "",     "Size",     "Size",   "",       "",
				   "Date", "Time", "Unzipped", "Zipped", "Offset", "Name",
				   "-------------------------------------------------------------------------------");

	return header;
}


String
ResourceEntryWriter::tocDetails()
{
	String		details;
	String		_date;
	String		_time;
	time_t		_time_t = (time_t)m_timestamp;
	struct tm *	stm;


	stm = localtime(&_time_t);
	if (m_timestamp && stm)
	{
		_date.sprintf("%s%d/%s%d/%d",
			stm->tm_mon+1 < 10 ? "0" : "", stm->tm_mon+1,
			stm->tm_mday < 10 ? "0" : "", stm->tm_mday, stm->tm_year + 1900);

		_time.sprintf("%s%d:%s%d:%s%d",
			stm->tm_hour < 10 ? "0" : "", stm->tm_hour,
			stm->tm_min  < 10 ? "0" : "", stm->tm_min,
			stm->tm_sec  < 10 ? "0" : "", stm->tm_sec);
	}
	else
	{
		_date = "--/--/----";
		_time = "--:--:--";
	}

	details.sprintf("  %-10S  %-8S  %8ld  %8ld  %8ld  %S",
		(const uint16 *)_date, (const uint16 *)_time,
		m_size, m_isizecompressed, m_ioffset, (const uint16 *)m_name);

	return details;
}


//----------------------------------------------------------------------------
// Private
//

/**
 * @fn ResourceEntryWriter::ResourceEntryWriter(ResourceEntryWriter & src)
 * @param src	Reference to resource entry which should be copied.
 *
 * This method is reserved and unimplemented.  It has been defined for
 * Orthodox Canonical Form (OCF) compliance to prevent shallow object copies
 * from inadvertent use.
 */

/**
 * @fn ResourceEntryWriter & ResourceEntryWriter::operator=(ResourceEntryWriter & src)
 * @param src	Reference to resource entry which should be copied.
 *
 * This method is reserved and unimplemented.  It has been defined for
 * Orthodox Canonical Form (OCF) compliance to prevent shallow object copies
 * from inadvertent use.
 */

//
// vim: nowrap
//
